%% (c) Copyright Bearice 2010.  All rights reserved. 
%% See COPYING for more copyrights details
%% TODO: Add description to minitrue_rpc_handler

-module(minitrue_rpc_handler).

%%
%% Include files
%%
-include("rpc.hrl").
-include_lib("yaws/include/yaws_api.hrl").
%%
%% Exported Functions
%%
-export([out/1]).

%%
%% API Functions
%%
out(A) ->
	HttpReq = A#arg.req,
	HttpMethod = HttpReq#http_request.method,
	ServiceName = case HttpMethod of
		'POST' -> A1=A,A#arg.appmoddata;
		'GET'  -> {S,R}=parse_url(A#arg.appmoddata),
				  A1=A#arg{state=R},S
	end,
	case minitrue_rpc_registry:find_service(ServiceName) of  %% a simple ets:lookup here
		[Service] ->
			handle_service(HttpMethod,Service,A1);
		[] ->
			error_response(404,"No such service in registry.")
	end.

parse_url(R)->
	parse_url(lists:reverse(R),[]).

parse_url([H|T],Acc)->
	case H of 
		$/ -> {lists:reverse(T),Acc};
		_  -> parse_url(T,[H|Acc])
	end.

handle_service(HttpMethod,Service,A)->
	case parse_payload(HttpMethod,A) of 
		{ok,Request,ID}->
			handle_request(Service,Request,ID,A);
		{error, Reason}->
			error_response(500,"Failed to parse request",Reason)
	end.

handle_request(Service,{call,Method,Args},ID,A)->
	case list_to_binary(Method) of 
		<<"system.listMethods">> ->
			rpc_response(ID,system_list_method(Service));
		<<"system.", _Rest/binary>> ->
			error_response(403, "System methods forbidden", Method,ID);
		_ ->
			case lists:keysearch(Method, #service_proc.name, Service#service.procs) of
				{value,Proc}->
					handle_call(Service,Proc,Args,ID,A);
				false ->
					error_response(404, "Procedure not found", Method,ID)
			end
	end.

handle_call(Service,Proc,Args,ID,A)->
	Name = Proc#service_proc.name,
	Handler = Service#service.handler,
	case catch rpc_call(Handler,Name,Args,A) of
		{'EXIT', {{function_clause, _}, _}} ->
			error_response(404, "Unmatched procedure", Name, ID);
		{'EXIT', Reason} ->
	    	error_response(500, "Internal error", Reason, ID);
		Response ->
		    rpc_response(ID, Response)
	end.

rpc_call({function,M,F},Name,Args,HttpRequest)->
	M:F({jsonrpc,Name,Args,HttpRequest});

rpc_call({pid,Pid},Name,Args,HttpRequest)->
	gen_server:call(Pid,{jsonrpc,Name,Args,HttpRequest}).

parse_payload('GET',A)->
	{ok,{call, A#arg.state,[]},null};
parse_payload('POST',A)->
	Payload = binary_to_list(A#arg.clidata),
	try 
        {ok, Obj} = json:decode_string(Payload),
        Method = jsonrpc:s(Obj, method),
        {array, Args} = jsonrpc:s(Obj, params),
        ID = jsonrpc:s(Obj, id),
        {ok, {call, Method, Args}, ID}
    catch 
        error:Err ->
           {error, Err}
    end.

system_list_method(#service{procs = Procs}) ->
	{array,[P#service_proc.name || P<-Procs]}.

error_response(Code,Message)->
	error_response(Code,Message,null,null).

error_response(Code,Message,Reason) ->
	error_response(Code,Message,Reason,null).

error_response(Code,Message,Reason,ID)->
	Obj={struct,[{"version","1.1"},
				 {"id",ID},
				 {"error",{struct,[{"code",Code},
								   {"message", Message},
				 				   {"error", (case Reason of 
												  null-> null; 
												  _ -> lists:flatten(io_lib:format("~p", [Reason])) 
											  end)}]}}]},
	send_json(Code,Obj).

rpc_response(ID,{error,Error})->
	Obj = {struct,[{"version","1.1"},
				   {"id",ID},
				   {"error",Error}]},
	send_json(200,Obj);
rpc_response(ID,{result,Result})->
	Obj = {struct,[{"version","1.1"},
				   {"id",ID},
				   {"result",Result}]},
	send_json(200,Obj).

send_json(Code,Obj)->
	J=json:encode(Obj),
	%io:format("~s", [J]),
	[{status, Code},
	 {content, "text/plain; charset=utf-8", J}].

